﻿//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using System.Text;
using OpenLS.Core;
using OpenLS.Spreadsheet.Extensibility;

namespace OpenLS.Spreadsheet.Formats
{
    public abstract class TextFormat: SpreadsheetFormat
    {
        
        struct Pair<T1, T2>
        {
            public T1 First;
            public T2 Second;
        }
        protected abstract char Separator { get; }

        public override SpreadsheetDocument Open(Stream stream)
        {
            using (var textReader = new StreamReader(stream, Encoding.Default))
            {
                SpreadsheetDocument result = SpreadsheetDocument.Create();
                using (result.Workbook.CreateUndoManagerDisabler())
                {
                    using (result.Workbook.CreateLoadingCalculationDisabler())
                    {
                        Worksheet worksheet = (Worksheet) result.Workbook.Sheets[0];
                        int row = 0;
                        var p = new Dictionary<string, Spreadsheet.Pair<object, Format>>();
                        using (worksheet.CreateWorksheetCellAddedEventDisabler())
                        {
                            using (worksheet.CreateCellValueChangedDisabler())
                            {
                                foreach (var values in getLines(textReader))
                                {
                                    int column = 0;
                                    worksheet.Rows[row].AdviseMinMax(0, values.Count);
                                    foreach (var text in values)
                                    {
                                        Spreadsheet.Pair<object, Format> pair;
                                        string formula = null;
                                        if (!p.TryGetValue(text, out pair))
                                        {
                                            if (text.StartsWith("="))
                                            {
                                                formula = text;
                                                pair = new Spreadsheet.Pair<object, Format>(null, null);
                                            }
                                            else
                                            {
                                                object value;
                                                Format format;
                                                CellFormatter.ParseValueAndFormat(text, out value, out format);
                                                pair = new Spreadsheet.Pair<object, Format>(value, format);
                                                p.Add(text, pair);
                                            }
                                        }

                                        if (pair.First != null)
                                        {
                                            worksheet.EnsurePromotedCell(row, column).setRawValue(pair.First);
                                        }
                                        if (pair.Second != null)
                                        {
                                            worksheet.EnsurePromotedCell(row, column).Format = pair.Second;
                                        }
                                        if (formula != null)
                                        {
                                            worksheet.EnsurePromotedCell(row, column).Formula = formula;
                                        }
                                        column++;
                                    }
                                    row++;
                                }
                            }
                        }
                    }
                }
                return result;
            }
        }
        public override void Save(SpreadsheetDocument document, Stream stream)
        {
            using (TextWriter writer = new StreamWriter(stream))
            {
                var worksheet = Enumerable.FirstOrDefault(Enumerable.OfType<Worksheet>(document.Workbook.Sheets));
                if (worksheet == null)
                    return;
                var r = worksheet.UsedRange;
                foreach (var rowRange in r.GetRowRanges(RangeType.Static))
                {
                    foreach (var cell in rowRange.GetAllCells())
                    {
                        object v = cell.Value;
                        if (v != null)
                            writer.Write(FormattedValue(v));
                        writer.Write(this.Separator);
                    }
                    writer.Write(Environment.NewLine);
                }
            }
        }

        private string FormattedValue(object o)
        {
            string s = o.ToString();
            if (s.Length == 0)
                return s;
            bool needsQuote = char.IsWhiteSpace(s[0]) || char.IsWhiteSpace(s[s.Length - 1]) || s.Contains("\"");
            string q = "\"";
            s = s.Replace(q, "\"\"");
            if (needsQuote)
                return q + s + q;
            return s;
        }

        private IEnumerable<IList<string>> getLines(TextReader reader)
        {
            bool inQuotedString = false;
            IList<string> line = new List<string>();
            var stringBuilder = new StringBuilder();
            while (reader.Peek() != -1)
            {
                var c = (char) reader.Read();
                if (c == '\n' || (c == '\r' && reader.Peek() == '\n'))
                {
                    if (c == '\r')
                    {
                        reader.Read();
                    }
                    if (inQuotedString)
                    {
                        if (c == '\r')
                        {
                            stringBuilder.Append('\r');
                        }
                        stringBuilder.Append('\n');
                    }
                    else
                    {
                        line.Add(stringBuilder.ToString().TrimEnd());

                        stringBuilder = new StringBuilder();
                        yield return line;
                        line = new List<string>();
                        inQuotedString = false;
                    }
                }
                else if (stringBuilder.Length == 0 && !inQuotedString)
                {
                    if (char.IsWhiteSpace(c) && c != Separator)
                    {
                        // Ignore leading whitespace           
                    }
                    else if (c == '"')
                    {
                        inQuotedString = true;
                    }
                    else if (c == Separator)
                    {
                        line.Add(stringBuilder.ToString().TrimEnd());
                        stringBuilder = new StringBuilder();
                    }
                    else
                    {
                        stringBuilder.Append(c);
                    }
                }
                else if (c == Separator)
                {
                    if (inQuotedString)
                    {
                        stringBuilder.Append(Separator);
                    }
                    else
                    {
                        line.Add(stringBuilder.ToString().TrimEnd());
                        stringBuilder = new StringBuilder();
                    }
                }
                else if (c == '"')
                {
                    if (inQuotedString)
                    {
                        if (reader.Peek() == '"')
                        {
                            reader.Read();
                            stringBuilder.Append('"');
                        }
                        else
                        {
                            inQuotedString = false;
                        }
                    }
                    else
                    {
                        stringBuilder.Append(c);
                    }
                }
                else
                {
                    stringBuilder.Append(c);
                }
            }
            line.Add(stringBuilder.ToString().TrimEnd());
            yield return line;
        }
    }
}
